/*!
 * Glide.js
 * Version: 1.0.6
 * Simple, lightweight and fast jQuery slider
 * Author: @JedrzejChalubek
 * Site: http://jedrzejchalubek.com/
 * Licensed under the MIT license
 */
;(function ($, window, document, undefined) {

  var name = 'glide',
    defaults = {

      // {Int or Bool} False for turning off autoplay
      autoplay: 4000,
      // {Bool} Pause autoplay on mouseover slider
      hoverpause: true,

      // {Bool} Circual play
      circular: true,

      // {Int} Animation time
      animationDuration: 500,
      // {String} Animation easing function
      animationTimingFunc: 'cubic-bezier(0.165, 0.840, 0.440, 1.000)',

      /**
       * {Bool or String} Show/hide/appendTo arrows
       * True for append arrows to slider wrapper
       * False for not appending arrows
       * Id or class name (e.g. '.class-name') for appending to specific HTML markup
       */
      arrows: true,
      // {String} Arrows wrapper class
      arrowsWrapperClass: 'slider-arrows',
      // {String} Main class for both arrows
      arrowMainClass: 'slider-arrow',
      // {String} Right arrow
      arrowRightClass: 'slider-arrow--right',
      // {String} Right arrow text
      arrowRightText: 'next',
      // {String} Left arrow
      arrowLeftClass: 'slider-arrow--left',
      // {String} Left arrow text
      arrowLeftText: 'prev',

      /**
       * {Bool or String} Show/hide/appendTo bullets navigation
       * True for append arrows to slider wrapper
       * False for not appending arrows
       * Id or class name (e.g. '.class-name') for appending to specific HTML markup
       */
      navigation: true,
      // {Bool} Center bullet navigation
      navigationCenter: true,
      // {String} Navigation class
      navigationClass: 'slider-nav',
      // {String} Navigation item class
      navigationItemClass: 'slider-nav__item',
      // {String} Current navigation item class
      navigationCurrentItemClass: 'slider-nav__item--current',

      // {Bool} Slide on left/right keyboard arrows press
      keyboard: true,

      // {Int or Bool} Touch settings
      touchDistance: 60,

      // {Function} Callback before plugin init
      beforeInit: function() {},
      // {Function} Callback after plugin init
      afterInit: function() {},

      // {Function} Callback before slide change
      beforeTransition: function() {},
      // {Function} Callback after slide change
      afterTransition: function() {}

    };

  /**
   * Slider Constructor
   * @param {Object} parent
   * @param {Object} options
   */
  function Glide(parent, options) {

    // Cache this
    var self = this;

    // Extend options
    this.options = $.extend({}, defaults, options);
    // Current slide id
    this.currentSlide = 0;
    // If CSS3 Transition isn't supported switch cssSupport variable to false and use $.animate()
    this.cssSupport = ( !this.css.isSupported("transition") || !this.css.isSupported("transform") ) ? false : true;
    // If circular set offset, two cloned slides
    this.offset = (this.options.circular) ? 2 : 0;

    // Callbacks before plugin init
    this.options.beforeInit.call(this);

    // Sidebar
    this.parent = parent;
    // Initialize
    this.init();
    // Start autoplay
    this.play();

    // Callback after plugin init
    this.options.afterInit.call(this);

    /**
     * API
     * Returning slider methods
     */
    return {

      /**
       * Get current slide number
       * @return {Int}
       */
      current: function() {
        return -(self.currentSlide) + 1;
      },

      /**
       * Reinit
       * Rebuild and recalculate dimensions of slider elements
       */
      reinit: function() {
        self.init();
      },

      /**
       * Start autoplay
       */
      play: function() {
        self.play();
      },

      /**
       * Stop autoplay
       */
      pause: function() {
        self.pause();
      },

      /**
       * Slide one forward
       * @param  {Function} callback
       */
      next: function(callback) {
        self.slide(1, false, callback);
      },

      /**
       * Slide one backward
       * @param  {Function} callback
       */
      prev: function(callback) {
        self.slide(-1, false, callback);
      },

      /**
       * Jump to specifed slide
       * @param  {Int}      distance
       * @param  {Function} callback
       */
      jump: function(distance, callback) {
        self.slide(distance-1, true, callback);
      },

      /**
       * Append navigation to specifet target
       * @param  {Mixed} target
       */
      nav: function(target) {

        /**
         * If navigation wrapper already exist
         * Remove it, protection before doubled navigation
         */
        if (self.navigation.wrapper) self.navigation.wrapper.remove();

        // While target isn't specifed, use slider wrapper
        self.options.navigation = (target) ? target : self.options.navigation;
        // Build
        self.navigation();

      },

      /**
       * Append arrows to specifet target
       * @param  {Mixed} target
       */
      arrows: function(target) {

        /**
         * If arrows wrapper already exist
         * Remove it, protection before doubled arrows
         */
        if (self.arrows.wrapper) self.arrows.wrapper.remove();

        // While target isn't specifed, use slider wrapper
        self.options.arrows = (target) ? target : self.options.arrows;
        // Build
        self.arrows();

      }

    };

  }

  /**
   * Building slider
   */
  Glide.prototype.build = function() {

    /**
     * Attatch bindings
     */
    this.bindings();

    /**
     * There is more than one slide
     */
    if (this.slides.length > 1) {
      /**
       * Circular
       * If circular option is true
       * Append left and right arrow
       */
      if (this.options.circular) this.circular();

      /**
       * Arrows
       * If arrows option is true
       * Append left and right arrow
       */
      if (this.options.arrows) this.arrows();

      /**
       * Navigation
       * If navigation option is true
       * Append navigation item for each slide
       */
      if (this.options.navigation) this.navigation();
    }

    /**
     * Attatch events
     */
    this.events();

  };

  /**
   * Build circular DOM elements
   * Clone first and last slide
   * Set wrapper width with addional slides
   * Move slider wrapper to first slide
   */
  Glide.prototype.circular = function() {

    /**
     * Clone first and last slide
     * and set width for each
     */
    this.firstClone = this.slides.filter(':first-child').clone().width(this.slides.spread);
    this.lastClone = this.slides.filter(':last-child').clone().width(this.slides.spread);

    /**
     * Append clodes slides to slider wrapper at the beginning and end
     * Increase wrapper with with values of addional slides
     * Clear translate and skip cloned last slide at the beginning
     */
    this.wrapper.append(this.firstClone).prepend(this.lastClone).width( this.parent.width() * (this.slides.length+2) )
      .trigger('clearTransition')
        .trigger('setTranslate', [-this.slides.spread]);

  };

  /**
   * Building navigation DOM
   */
  Glide.prototype.navigation = function() {

    this.navigation.items = {};

    // Navigation wrapper
    this.navigation.wrapper = $('<div />', {
      'class': this.options.navigationClass
    }).appendTo(
      /**
       * Setting append target
       * If option is true set default target, that is slider wrapper
       * Else get target set in options
       * @type {Bool or String}
       */
      (this.options.navigation === true) ? this.parent : this.options.navigation
    );

    for (var i = 0; i < this.slides.length; i++) {
      this.navigation.items[i] = $('<a />', {
        'href': '#',
        'class': this.options.navigationItemClass,
        // Direction and distance -> Item index forward
        'data-distance': i
      }).appendTo(this.navigation.wrapper);
    }

    // Add navCurrentItemClass to the first navigation item
    this.navigation.items[0].addClass(this.options.navigationCurrentItemClass);

    // If centered option is true
    if (this.options.navigationCenter) {
      // Center bullet navigation
      this.navigation.wrapper.css({
        'left': '50%',
        'width': this.navigation.wrapper.children().outerWidth(true) * this.navigation.wrapper.children().length,
        'margin-left': -(this.navigation.wrapper.outerWidth(true)/2)
      });
    }

  };

    /**
   * Building arrows DOM
   */
  Glide.prototype.arrows = function() {

    /**
     * Arrows wrapper
     * @type {Obejct}
     */
    this.arrows.wrapper = $('<div />', {
      'class': this.options.arrowsWrapperClass
    }).appendTo(
      /**
       * Setting append target
       * If option is true set default target, that is slider wrapper
       * Else get target set in options
       * @type {Bool or String}
       */
      (this.options.arrows === true) ? this.parent : this.options.arrows
    );

    /**
     * Right arrow
     * @type {Obejct}
     */
    this.arrows.right = $('<a />', {
      'href': '#',
      'class': this.options.arrowMainClass + ' ' + this.options.arrowRightClass,
      // Direction and distance -> One forward
      'data-distance': '1',
      'html': this.options.arrowRightText
    }).appendTo(this.arrows.wrapper);

    /**
     * Left arrow
     * @type {Object}
     */
    this.arrows.left = $('<a />', {
      'href': '#',
      'class': this.options.arrowMainClass + ' ' + this.options.arrowLeftClass,
      // Direction and distance -> One backward
      'data-distance': '-1',
      'html': this.options.arrowLeftText
    }).appendTo(this.arrows.wrapper);

  };

  /**
   * Function bindings
   */
  Glide.prototype.bindings = function() {

    var self = this,
      o = this.options,
      prefix = this.css.getPrefix();

    /**
     * Setup slider wrapper bindings
     * for translate and transition control
     */
    this.wrapper.bind({

      /**
       * Set transition
       */
      'setTransition': function() {
        $(this).css( prefix + 'transition', prefix + 'transform ' + o.animationDuration + 'ms ' + o.animationTimingFunc);
      },

      /**
       * Clear transition
       * for immediate jump effect
       */
      'clearTransition': function() {
        $(this).css( prefix + 'transition', 'none');
      },

      /**
       * Set translate value
       * @param  {Object} event
       * @param  {Ind} translate
       */
      'setTranslate': function(event, translate) {
        // if css3 suported set translate3d
        if (self.cssSupport) $(this).css( prefix + 'transform', 'translate3d(' + translate + 'px, 0px, 0px)');
        // if not set left margin
        else $(this).css('margin-left', translate);
      }

    });

  };

  /**
   * Events controllers
   */
  Glide.prototype.events = function() {

    /**
     * Swipe
     * If swipe option is true
     * Attach touch events
     */
    if (this.options.touchDistance) {
      this.parent.on({
        'touchstart MSPointerDown': $.proxy(this.events.touchstart, this),
        'touchmove MSPointerMove': $.proxy(this.events.touchmove, this),
        'touchend MSPointerUp': $.proxy(this.events.touchend, this),
      });
    }

    /**
     * Arrows
     * If arrows exists
     * Attach click event
     */
    if (this.arrows.wrapper) {
      $(this.arrows.wrapper).children().on('click touchstart',
        $.proxy(this.events.arrows, this)
      );
    }

    /**
     * Navigation
     * If navigation exists
     * Attach click event
     */
    if (this.navigation.wrapper) {
      $(this.navigation.wrapper).children().on('click touchstart',
        $.proxy(this.events.navigation, this)
      );
    }

    /**
     * Keyboard
     * If keyboard option is true
     * Attach press event
     */
    if (this.options.keyboard) {
      $(document).on('keyup.glideKeyup',
        $.proxy(this.events.keyboard, this)
      );
    }

    /**
     * Slider hover
     * If hover option is true
     * Attach hover event
     */
    if (this.options.hoverpause) {
      this.parent.on('mouseover mouseout',
        $.proxy(this.events.hover, this)
      );
    }

    /**
     * Slider resize
     * On window resize
     * Attach resize event
     */
    $(window).on('resize',
      $.proxy(this.events.resize, this)
    );

  };

  /**
   * Navigation event controller
   * On click in navigation item get distance
   * Then slide specified distance with jump
   */
  Glide.prototype.events.navigation = function(event) {

    if ( !this.wrapper.attr('disabled') ) {
      // Prevent default behaviour
      event.preventDefault();
      // Slide distance specified in data attribute
      this.slide( $(event.currentTarget).data('distance'), true );
    }

  };

  /**
   * Arrows event controller
   * On click in arrows get direction and distance
   * Then slide specified distance without jump
   * @param  {Obejct} event
   */
  Glide.prototype.events.arrows = function(event) {

    if ( !this.wrapper.attr('disabled') ) {
      // Prevent default behaviour
      event.preventDefault();
      // Slide distance specified in data attribute
      this.slide( $(event.currentTarget).data('distance'), false );
    }

  };

  /**
   * Keyboard arrows event controller
   * Keyboard left and right arrow keys press
   */
  Glide.prototype.events.keyboard = function(event) {

    if ( !this.wrapper.attr('disabled') ) {
      // Next
      if (event.keyCode === 39) this.slide(1);
      // Prev
      if (event.keyCode === 37) this.slide(-1);
    }

  };

  /**
   * When mouse is over slider, pause autoplay
   * On out, start autoplay again
   */
  Glide.prototype.events.hover = function(event) {

    // Pasue autoplay
    this.pause();

    // When mouse left slider or touch end, start autoplay anew
    if (event.type === 'mouseout') this.play();

  };

  /**
   * When resize browser window
   * Reinit plugin for new slider dimensions
   * Correct crop to current slide
   */
  Glide.prototype.events.resize = function(event) {

    // Reinit plugin (set new slider dimensions)
    this.dimensions();
    // Crop to current slide
    this.slide(0);

  };

  /**
   * Disable events thats controls slide changes
   */
  Glide.prototype.disableEvents = function() {
    this.wrapper.attr( "disabled", true );
  };

  /**
   * Enable events thats controls slide changes
   */
  Glide.prototype.enableEvents = function() {
    this.wrapper.attr( "disabled", false );
  };

  /**
  * Touch start
  * @param  {Object} e event
  */
  Glide.prototype.events.touchstart = function(event) {

    // Cache event
    var touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0];

    // Get touch start points
    this.events.touchStartX = touch.pageX;
    this.events.touchStartY = touch.pageY;
    this.events.touchSin = null;

  };

  /**
  * Touch move
  * From swipe length segments calculate swipe angle
  * @param  {Obejct} e event
  */
  Glide.prototype.events.touchmove = function(event) {

    // Cache event
    var touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0];

    // Calculate start, end points
    var subExSx = touch.pageX - this.events.touchStartX;
    var subEySy = touch.pageY - this.events.touchStartY;
    // Bitwise subExSx pow
    var powEX = Math.abs( subExSx << 2 );
    // Bitwise subEySy pow
    var powEY = Math.abs( subEySy << 2 );
    // Calculate the length of the hypotenuse segment
    var touchHypotenuse = Math.sqrt( powEX + powEY );
    // Calculate the length of the cathetus segment
    var touchCathetus = Math.sqrt( powEY );

    // Calculate the sine of the angle
    this.events.touchSin = Math.asin( touchCathetus/touchHypotenuse );

    if ( (this.events.touchSin * (180 / Math.PI)) < 45 ) event.preventDefault();

  };

  /**
  * Touch end
  * @param  {Object} e event
  */
  Glide.prototype.events.touchend = function(event) {

    // Cache event
    var touch = event.originalEvent.touches[0] || event.originalEvent.changedTouches[0];

    // Calculate touch distance
    var touchDistance = touch.pageX - this.events.touchStartX;

    // While touch is positive and greater than distance set in options
    if ( (touchDistance > this.options.touchDistance) && ( (this.events.touchSin * (180 / Math.PI)) < 45) ) {
      // Slide one backward
      this.slide(-1);
    // While touch is negative and lower than negative distance set in options
    } else if (
      (touchDistance < -this.options.touchDistance) && ( (this.events.touchSin * (180 / Math.PI)) < 45) ) {
      // Slide one forward
      this.slide(1);
    }

  };

  /**
   * Slides change & animate logic
   * @param  {int} distance
   * @param  {bool} jump
   * @param  {function} callback
   */
  Glide.prototype.slide = function(distance, jump, callback) {

    /**
     * Stop autoplay
     * Clearing timer
     */
    this.pause();

    // Callbacks before slide change
    this.options.beforeTransition.call(this);

    // Setup variables
    var self = this,
      currentSlide = (jump) ? 0 : this.currentSlide,
      slidesLength = -(this.slides.length-1),
      fromFirst = false,
      fromLast = false;

    /**
     * Check if current slide is first and direction is previous, then go to last slide
     * or current slide is last and direction is next, then go to the first slide
     * else change current slide normally
     */
    if ( currentSlide === 0 && distance === -1 ) {
      fromFirst = true;
      currentSlide = slidesLength;
    } else if ( currentSlide === slidesLength && distance === 1 ) {
      fromLast = true;
      currentSlide = 0;
    } else {
      currentSlide = currentSlide + (-distance);
    }

    /**
     * Crop to current slide.
     * Mul slide width by current slide number.
     */
    var offset = this.slides.spread * currentSlide;

    /**
     * While circular decrease offset with the width of single slide
     * When fromFirst and fromLast flags are set, unbind events thats controls changing
     * When fromLast flags is set, set offset to slide width mulled by slides count without cloned slides
     * When fromFirst flags is set, set offset to zero
     */
    if (this.options.circular) {
      offset = offset - this.slides.spread;
      if (fromLast || fromFirst) this.disableEvents();
      if (fromLast) offset = this.slides.spread * (slidesLength - 2);
      if (fromFirst) offset = 0;
    }

    /**
     * Slide change animation
     * While CSS3 is supported use offset
     * if not, use $.animate();
     */
    if (this.cssSupport) this.wrapper.trigger('setTransition').trigger('setTranslate', [offset]);
    else this.wrapper.stop().animate({ 'margin-left': offset }, this.options.animationDuration);

    /**
     * While circular
     */
    if (this.options.circular) {

      /**
       *  When fromFirst and fromLast flags are set
       *  after animation clear transition and bind events that control slides changing
       */
      if (fromFirst || fromLast) {
        this.afterAnimation(function(){
          self.wrapper.trigger('clearTransition');
          self.enableEvents();
        });
      }

      /**
       * When fromLast flag is set
       * after animation make immediate jump from cloned slide to proper one
       */
      if (fromLast) {
        this.afterAnimation(function(){
          fromLast = false;
          self.wrapper.trigger('setTranslate', [-self.slides.spread]);
        });
      }

      /**
       * When fromFirst flag is set
       * after animation make immediate jump from cloned slide to proper one
       */
      if (fromFirst) {
        this.afterAnimation(function(){
          fromFirst = false;
          self.wrapper.trigger('setTranslate', [self.slides.spread * (slidesLength-1)]);
        });
      }

    }

    // Set to navigation item current class
    if (this.options.navigation && this.navigation.wrapper) {
      $(this.parent).children('.' + this.options.navigationClass).children()
        .eq(-currentSlide)
          .addClass(this.options.navigationCurrentItemClass)
            .siblings()
              .removeClass(this.options.navigationCurrentItemClass);
    }

    // Update current slide globaly
    this.currentSlide = currentSlide;

    // Callbacks after slide change
    this.afterAnimation(function(){
      self.options.afterTransition.call(self);
      if ( (callback !== 'undefined') && (typeof callback === 'function') ) callback();
    });

    /**
     * Start autoplay
     * Setting up timer
     */
    this.play();

  };

  /**
   * Autoplay logic
   * Setup counting
   */
  Glide.prototype.play = function() {

    // Cache this
    var self = this;

    /**
     * If autoplay turn on
     * Slide one forward after a set time
     */
    if (this.options.autoplay) {
      this.auto = setInterval(function() {
        self.slide(1, false);
      }, this.options.autoplay);
    }

  };

  /**
   * Autoplay pause
   * Clear counting
   */
  Glide.prototype.pause = function() {

    /**
     * If autoplay turn on
     * Clear interial
     */
    if (this.options.autoplay) this.auto = clearInterval(this.auto);

  };

  /**
   * Call callback after animation duration
   * Added 10 ms to duration to be sure is fired after animation
   * @param  {Function} callback
   */
  Glide.prototype.afterAnimation = function(callback) {

    setTimeout(function(){
      callback();
    }, this.options.animationDuration + 10);

  };

  /**
   * Dimensions
   * Get & set dimensions of slider elements
   */
  Glide.prototype.dimensions = function() {

    // Get slide width
    this.slides.spread = this.parent.width();
    // Set wrapper width
    this.wrapper.width(this.slides.spread * (this.slides.length + this.offset));
    // Set slide width
    this.slides.add(this.firstClone).add(this.lastClone).width(this.slides.spread);

  };

  /**
   * Initialize
   * Set wrapper
   * Set slides
   * Set animation type
   */
  Glide.prototype.init = function() {

    // Set slides wrapper
    this.wrapper = this.parent.children();
    // Set slides
    this.slides = this.wrapper.children();
    // Set slider dimentions
    this.dimensions();

    // Build DOM
    this.build();

  };


  /**
   * Methods for css3 management
   */
  Glide.prototype.css = {

    /**
     * Check css3 support
     * @param  {String}  Declaration name to check
     * @return {Boolean}
     */
    isSupported: function(declaration) {

      var isSupported = false,
        prefixes = 'Khtml ms O Moz Webkit'.split(' '),
        clone = document.createElement('div'),
        declarationCapital = null;

      declaration = declaration.toLowerCase();
      if (clone.style[declaration] !== undefined) isSupported = true;
      if (isSupported === false) {
        declarationCapital = declaration.charAt(0).toUpperCase() + declaration.substr(1);
        for( var i = 0; i < prefixes.length; i++ ) {
          if( clone.style[prefixes[i] + declarationCapital ] !== undefined ) {
            isSupported = true;
            break;
          }
        }
      }

      if (window.opera) {
        if (window.opera.version() < 13) isSupported = false;
      }

      if (isSupported === 'undefined' || isSupported === undefined) isSupported = false;

      return isSupported;

    },

    /**
     * Get browser css prefix
     * @return {String}   Returns prefix in "-{prefix}-" format
     */
    getPrefix: function () {

      if (!window.getComputedStyle) return '';

      var styles = window.getComputedStyle(document.documentElement, '');
      return '-' + (Array.prototype.slice
        .call(styles)
        .join('')
        .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
      )[1] + '-';

    }

  };

  $.fn[name] = function(options) {

    return this.each(function () {
      if ( !$.data(this, 'api_' + name) ) {
        $.data(this, 'api_' + name,
          new Glide($(this), options)
        );
      }
    });

  };

})(jQuery, window, document);